#ifndef MAPREDUCE_MAP_
#define MAPREDUCE_MAP_

#include <functional>
#include <omp.h>

#include "common"
#include "range"

namespace map_reduce {

namespace map_sched {
    enum base_policy {
        always = 5,
        root   = 6,
        never  = 7
    };

    template <unsigned Level = 0>
    struct parallel {
        static const base_policy when = root;
        static const unsigned level = Level;
    };

    template <unsigned Level = 0>
    struct parallel_always {
        static const base_policy when = always;
        static const unsigned level = Level;
    };

    struct serial {
        static const base_policy when = never;
        static const unsigned level = 0;
    };

    //using automatic = parallel<0>;
    using automatic = serial;
};

template <unsigned Level, typename Func, typename Range, typename Policy>
struct wrapper_map {
    template<typename... Args>
    inline
    static void
    map(Func f, Range r, Args... args)
    {
        using mydim = typename Range::dim_type;

        mydim d = r.get_dim(sizeof...(Args));

        if ((Policy::when == map_sched::base_policy::always &&
             Policy::level == Range::NDims - Level) ||
            (Policy::when == map_sched::base_policy::root &&
             Policy::level == Range::NDims - Level &&
             !in_parallel)) {
            #pragma omp parallel for
            for (typename Range::type t  = d.begin;
                                      t  < d.end;
                                      t += d.step) {
                //in_parallel = true;
                wrapper_map<Level - 1, Func, Range, Policy>::map(f, r, args..., t);
            }
        } else {
            for (typename Range::type t  = d.begin;
                                      t  < d.end;
                                      t += d.step) {
                wrapper_map<Level - 1, Func, Range, Policy>::map(f, r, args..., t);
            }
        }
    }

    inline
    static void
    map(Func f, Range r)
    {
        using mydim = typename Range::dim_type;

        mydim d = r.get_dim(0);

        if ((Policy::when == map_sched::base_policy::always &&
             Policy::level == Range::NDims - Level) ||
            (Policy::when == map_sched::base_policy::root &&
             Policy::level == Range::NDims - Level &&
             !in_parallel)) {
            #pragma omp parallel for
            for (typename Range::type t  = d.begin;
                                      t  < d.end;
                                      t += d.step) {
                //in_parallel = true;
                wrapper_map<Level - 1, Func, Range, Policy>::map(f, r, t);
            }

            if (Range::NDims == Level) {
                //in_parallel = false;
            }
        } else {
            for (typename Range::type t  = d.begin;
                                      t  < d.end;
                                      t += d.step) {
                wrapper_map<Level - 1, Func, Range, Policy>::map(f, r, t);
            }

            if (Range::NDims == Level) {
                //in_parallel = false;
            }
        }
    }
};

template <typename Func, typename Range, typename Policy>
struct wrapper_map<0, Func, Range, Policy> {
    template<typename... Args>
    inline
    static void
    map(Func f, Range r, Args ... args)
    {
        f(args...);
    }
};

template <typename Func, typename Range, typename Policy>
inline
static
void map(Func f, Range r, const Policy &/* p */)
{
    wrapper_map<Range::NDims, Func, Range, Policy>::map(f, r);
}

template <typename Func, typename Range>
inline
static
void map(Func f, Range r)
{
    map(f, r, map_sched::automatic());
}

}

#endif

/* vim:set ft=cpp backspace=2 tabstop=4 shiftwidth=4 textwidth=120 foldmethod=marker expandtab: */
